package com.laght.jdkdemo.jdk21demo;

import com.laght.jdkdemo.jdk8demo.functionInterface.FunctionImplDemoA;
import com.laght.jdkdemo.jdk8demo.functionInterface.FunctionImplDemoB;
import com.laght.jdkdemo.jdk8demo.functionInterface.FunctionInterfaceDemo;
import com.laght.jdkdemo.jdk8demo.interfaceenhance.InterfaceEnhanceDemo;
import com.laght.jdkdemo.jdk8demo.methodquote.HandleFunctionInterfaceDemo;
import com.laght.jdkdemo.jdk8demo.methodquote.MethodQuoteDemo;
import io.micrometer.observation.Observation;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;

/**
 * java 8 新特性演示案例
 * 1、函数式接口
 * 2、Lambda表达式
 * 3、方法引用|构造器引用
 * 4、stream Api
 * 5、接口增强
 * 6、新的事件类和日期API
 * 7、Optional类
 */
@SpringBootTest
class Jdk8Demo {

    /**
     * 函数式接口
     * java.util.function.Function<T,R> （有输入，有输出：根据T类型，得到R类型）
     * java.util.function.Predicate<T> （有输入，有输出：输出布尔值）
     * java.util.function.Consumer<T> （有输入，无输出）
     * java.util.function.Supplier<T> （无输入，有输出：只有指定类型的输出）
     * 优点：可以将需要封装的代码单独抽离，方便随时复用，可以做回调处理
     * 缺点：随着业务的复杂性提高，接口在方法之中传递，容易增加代码复杂性，不易阅读
     * 适用场景：公共的基础逻辑需要封装，比如参数计算，数据过滤，数据解析封装等等
     */
    @Test
    void functionInterfaceTest() {
        String a = "hello";
        String b = "world";
        functionInterfaceChildrenMethod1(new FunctionImplDemoA(), a, b);

        functionInterfaceChildrenMethod1(new FunctionImplDemoB(), a, b);

        functionInterfaceChildrenMethod1(new FunctionInterfaceDemo<Integer, Integer>() {
            @Override
            public void handleFunction(Integer a, Integer b) {
                System.out.println("I am lambda : " + a+b);
            }

        }, a, b);
    }

    private void functionInterfaceChildrenMethod1(FunctionInterfaceDemo functionInterfaceDemo, String a, String b) {
        functionInterfaceDemo.handleFunction(a,b);
    }


    /**
     * Lambda表达式
     * 使用Lambda表达式，实现四种函数式接口，为了简化函数式接口的使用
     * 优点：可以直接生命一个函数式接口，无需做出实现类，写法简单
     * 缺点：学习成本高，可读性差
     * 适用范围：函数式变成，排序，线程和并发变成简化简称的创建和管理过程
     */
    @Test
    void lambdaTest() {
        // 无参无返回值
        try {
            Observation.CheckedRunnable eCheckedRunnable = () -> {
                System.out.println("无参无返回值，Lambda表达式");
            };
            eCheckedRunnable.run();
        } catch (Throwable e) {
            e.printStackTrace();
        }

        // 无参有返回值
        Supplier<String> supplier = () -> {
            System.out.println("无参有返回值，Lambda表达式");
            String response = "我是生产者";
            System.out.println("出参：" + response);
            return response;
        };
        System.out.println(supplier.get());

        // 有参无返回值
        Consumer<String> consumer = (request) -> {
            System.out.println("有参无返回值，Lambda表达式" + request);
            System.out.println("入参：" + request);
        };
        consumer.accept("我是消费者");

        // 有参有返回值
        Comparator<Integer> comparator = (request1,request2) -> {
            System.out.println("有参有返回值，我是比较器，Lambda表达式");
            System.out.println("入参1：" + request1);
            System.out.println("入参2：" + request2);
            return request1.compareTo(request2);
        };
        System.out.println(comparator.compare(1, 1));

        Predicate<String> predicate = (request) -> {
            System.out.println("有参有返回值固定布尔，我是断言，Lambda表达式");
            System.out.println("入参：" + request);
            return request.equals("我是断言");
        };
        System.out.println(predicate.test("我是断言"));



        Function<Integer, String> function = (request) -> {
            System.out.println("有参有返回值，我是函数，Lambda表达式");
            System.out.println("入参：" + request);

            String response = request.toString();
            System.out.println("出参：" + response);
            return response;
        };
        System.out.println(function.apply(10086));
    }

    /**
     * 方法引用|构造器引用
     * 为了简化Lambda表达式的使用，需根据方法的出入参不同，选择使用不同的函数式接口接收执行
     * 优点：可以将一些复杂的lambda表达式，转化为简洁明了的代码，可以重复使用已经存在的方法
     * 缺点：方法名并不直观或者存在多个参数的情况下，可能会降低可读性。具有方法引用只能用于引用非静态方法，静态方法和够着方法，其他情况无法使用的局限性
     * 适用范围：当要将值传给lambda体，已经有实现的方法，不用自己在lambda体重谢多行逻辑代码
     */
    @Test
    void methodQuoteTest() {
        //方法引用
        // 使用格式（对象实例::非静态方法名）
        MethodQuoteDemo methodQuoteDemo = new MethodQuoteDemo("对象实例1");

        HandleFunctionInterfaceDemo handleFunctionInterfaceDemo = methodQuoteDemo::noStaticMethod;
        handleFunctionInterfaceDemo.excutor();// noStaticMethod方法无参无返回，所以使用HandleFunctionInterfaceDemo接收执行


        // 使用格式（类名::静态方法名）
        Consumer<String> consumer1 = MethodQuoteDemo::staticMethod;
        consumer1.accept("对象实例2");// staticMethod有参无返回，所以使用Consumer接收执行

        // 使用格式（类名::非静态方法名）
        Consumer<MethodQuoteDemo> noStaticMethod = MethodQuoteDemo::noStaticMethod;
        noStaticMethod.accept(new MethodQuoteDemo("对象实例3"));//noStaticMethod方法无参无返回，但是需要传入类对应的对象实例，所以使用Consumer接收执行




        // 构造器引用（注意只能无参构造器才能使用）
        // 无参构造器
        HandleFunctionInterfaceDemo handleFunctionInterfaceDemo1 = () -> new MethodQuoteDemo();
        handleFunctionInterfaceDemo1.excutor();
        HandleFunctionInterfaceDemo handleFunctionInterfaceDemo2 = MethodQuoteDemo::new;
        handleFunctionInterfaceDemo2.excutor();

    }

    /**
     * stream Api
     * Stream可以由数组或集合创建，对流的操作分为两种：
     * 中间操作：每次返回一个新的流，可以有多个。
     * 终端操作：每个流只能进行一次终端操作，终端操作结束后流无法再次使用，终端操作会产生一个新的集合或值。
     * 优点：代码更加简洁、支持对集合或数组中的元素并行处理、懒加载只会在执行终端操作的时候才会执行前面的中间操作，可以处理集合、数组、IO流、生成器等多种数据源，支持多种操作并可以组合使用
     * 缺点：学习成本高，代码可读性略低
     * 适用范围：集合中的元素进行操作，比如：筛选、排序、聚合等
     */
    @Test
    void streamApiTest(){
        List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
        // 过滤
        List<Integer> filterList = list.stream().filter(f -> f == 10).collect(Collectors.toList());
        System.out.println("过滤结果：");
        filterList.stream().forEach(System.out::println);
        // 映射
        List<String> mapList = list.stream().map(f -> "类型"+f).collect(Collectors.toList());// 由int类型，映射为string类型
        System.out.println("映射结果：");
        mapList.stream().forEach(System.out::println);
        // 排序
        List<Integer> sortList = list.stream().sorted((s1,s2)->s2.compareTo(s1)).collect(Collectors.toList());
        System.out.println("排序结果：");
        sortList.stream().forEach(System.out::println);
        // 聚合
        Integer reduce = list.stream().reduce(0, (a, b) -> a + b);
        System.out.println("聚合结果：");
        System.out.println(reduce);
    }

    /**
     * 接口增强
     * 增强之前：静态常量、抽象方法
     * 增强之后：静态常量、抽象方法、默认方法、静态方法
     * 优点：用于后期增加新功能时，不需要将所有实现类再同时实现改方法
     * 缺点：多重继承的可读复杂性提升，需要了解默认方法是否在子类继承，从而推断是先走接口的默认方法，还是实现类中实现的默认方法
     * 使用范围：对接口进行功能扩展时使用
     */
    @Test
    void interfaceEnhanceTest(){
        // 调用静态方法
        InterfaceEnhanceDemo.staticMethod();
        //调用默认方法
        InterfaceEnhanceDemo interfaceEnhanceDemo = new InterfaceEnhanceDemo() {
            @Override
            public void abstractMethod() {

            }
//            @Override
//            public void defaultMethod() {
//
//            }
        };
        interfaceEnhanceDemo.defaultMethod();
    }

    /**
     * 新的时间类和日期API
     * 本地日期和时间类：LocalDateTime，LocalDate，LocalTime；
     * 带时区的日期和时间类：ZonedDateTime；
     * 时刻类：Instant
     * 时区：ZoneId，ZoneOffset
     * 时间间隔：Duration
     * 优点：更精确的事件，更方便的计算，更简单的格式化
     * 缺点：需要重新学习LocalDateTime、ZonedDateTime、Instant、ZoneId、Duration等相关类的使用
     * 适用场景：关于时间计算方面使用
     */
    @Test
    void dateAndTimeTest(){
        //获取到当前的日期和时间
        LocalDateTime now = LocalDateTime.now();
        LocalDate localDate = now.toLocalDate();//转换到当前日期
        LocalTime localTime = now.toLocalTime();//转换到当前时间
        System.out.println("当前日期时间："+now);
        System.out.println("当前日期："+localDate);
        System.out.println("当前时间："+localTime);


        //根据指定日期转为LocalDateTime
        LocalDate ofLocalDate = LocalDate.of(2023, 12, 13);
        LocalTime ofLocalTime = LocalTime.of(16, 39, 40);
        System.out.println("指定日期："+ofLocalDate);
        System.out.println("指定时间："+ofLocalTime);
        LocalDateTime ofLocalDateTime1 = LocalDateTime.of(ofLocalDate, ofLocalTime);
        LocalDateTime ofLocalDateTime2 = LocalDateTime.of(2023, 12, 13,16, 39, 40);
        System.out.println("指定日期时间1："+ofLocalDateTime1);
        System.out.println("指定日期时间1："+ofLocalDateTime2);

        // 解析时间字符串，T是日期和时间的分隔符
        // 日期：yyyy-MM-dd
        // 时间：HH:mm:ss
        // 带毫秒的时间：HH:mm:ss.SSS
        // 日期和时间：yyyy-MM-dd'T'HH:mm:ss
        // 带毫秒的日期和时间：yyyy-MM-dd'T'HH:mm:ss.SSS
        LocalDateTime parseLocalDateTime = LocalDateTime.parse("2023-12-13T16:39:52.789");
        LocalDate parseLocalDate = LocalDate.parse("2023-12-13");
        LocalTime parseLocalTime = LocalTime.parse("16:39:52");
        System.out.println("解析日期时间："+parseLocalDateTime);
        System.out.println("解析日期："+parseLocalDate);
        System.out.println("解析时间："+parseLocalTime);

        // 时间加减方法（plusXxx()、minusXxx()）
        LocalDateTime plusLocalDateTime = now.plusYears(1).plusMonths(1).plusDays(1).plusHours(1).plusMinutes(1).plusSeconds(1).plusNanos(1);
        System.out.println("当前日期时间全+1："+plusLocalDateTime);
        LocalDateTime minusLocalDateTime = now.minusYears(1).minusMonths(1).minusDays(1).minusHours(1).minusMinutes(1).minusSeconds(1).minusNanos(1);
        System.out.println("当前日期时间全-1："+minusLocalDateTime);

        // 时间调整方法（withXxx()）
        //withDayOfMonth(1)调整到当前月的第一天，withDayOfYear(1)调整到当前年的第一天
        LocalDateTime withLocalDateTime = now.withYear(1).withMonth(1).withDayOfMonth(1).withHour(1).withMinute(1).withSecond(1).withNano(1);
        System.out.println("调整日期时间全："+withLocalDateTime);

        //判断大小isBefore()与isAfter()方法
        System.out.println("plusLocalDateTime < minusLocalDateTime：" + plusLocalDateTime.isBefore(minusLocalDateTime));
        System.out.println("plusLocalDateTime > minusLocalDateTime：" + plusLocalDateTime.isAfter(minusLocalDateTime));

        // 判断时间间隔（Duration）、天数间隔（Period）
        Duration duration = Duration.between(plusLocalDateTime, minusLocalDateTime);
        System.out.println("时间间隔：" + duration);//PT-19058H-2M-2.000000002S 间隔19058小时，2月，2.000000002秒
        Period period = Period.between(plusLocalDateTime.toLocalDate(), minusLocalDateTime.toLocalDate());
        System.out.println("日期间隔：" + period);//P-2Y-2M-2D 间隔2年，2月，2天

        // 给本地时间添加系统默认时区得到带时区的时间
        ZonedDateTime zonedDateTime = now.atZone(ZoneId.systemDefault());
        ZonedDateTime New_YorkZonedDateTime = now.atZone(ZoneId.of("America/New_York"));
        System.out.println("当前默认时区日期时间："+zonedDateTime);
        System.out.println("当前纽约时区日期时间："+New_YorkZonedDateTime);

        //调整时区
        zonedDateTime = zonedDateTime.withZoneSameInstant(ZoneId.of("America/New_York"));
        System.out.println("当前默认时区日期时间-调整时区为纽约："+zonedDateTime);

        //格式化显示（DateTimeFormatter是线程安全的不可变对象）注意，只支持新的LocalDateTime和ZonedDateTime
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss ZZZZ '中国时间'");//全局不可变且线程安全
        System.out.println(dateTimeFormatter.format(zonedDateTime));//2023-12-13T04:19:43 GMT-05:00 中国时间

        //Instant.now()替代System.currentTimeMillis()，获取1970年1月1日0分0秒至今的时间戳（优点是更精确）
        Instant instant = Instant.now();
        System.out.println("1970年1月1日0分0秒至今："+instant);
        System.out.println("1970年1月1日0分0秒至今："+instant.getEpochSecond() + "秒时间戳");
        System.out.println("1970年1月1日0分0秒至今："+instant.toEpochMilli() + "毫秒时间戳");

        // 新旧转换
        //Date转Instant
        new Date().toInstant();
        //Calendar转Instant
        Calendar.getInstance().toInstant();
        //Calendar转ZonedDateTime
        Calendar calendar = Calendar.getInstance();
        calendar.toInstant().atZone(calendar.getTimeZone().toZoneId());

        // instant转Date
        new Date(instant.getEpochSecond()*1000);
        // ZonedDateTime转Calendar
        Calendar calendar1 = Calendar.getInstance();
        calendar1.clear();
        calendar1.setTimeZone(TimeZone.getTimeZone(zonedDateTime.getZone().getId()));
        calendar1.setTimeInMillis(zonedDateTime.toEpochSecond()*1000);
        System.out.println();
    }

    /**
     * Optional类
     * 优点：可以更好的检测非空值以及为null的情况下提供替代值的方式，Optional类实例化后的值不可变（初始化时值为null的情况除外），避免空指针异常，提升程序的健壮性
     * 缺点：需要精力学习使用
     * 适用场景：适用于处理null值
     */
    @Test
    void optionalTest(){
        // 构造一个空对象
        Optional<Integer> optional = Optional.empty();

        // 构造一个不为空的对象
        optional = Optional.of(1);
//        optional = Optional.of(null);//空指针

        // 构造一个可为空也可不为空的对象
        optional = Optional.ofNullable(null);
        System.out.println("Optional是否为空：" + optional.isPresent());
        optional = Optional.ofNullable(1);
        System.out.println("Optional是否为空：" + optional.isPresent());


        // 如果不为空者执行函数式接口，但是内容不会被改变
        optional.ifPresent(new Consumer<Integer>() {
            @Override
            public void accept(Integer integer) {
                if (integer != null){
                    integer = integer + 99;
                }
            }
        });

        //获取内容值
        System.out.println("Optional内容为：" + optional.get());


        // 映射处理得到另一个Optional对象（普通类型的对象）
        Optional<String> mapOptional = optional.map(v -> v.toString());
        System.out.println("映射：" + mapOptional.get());

        // 接受一个函数作为参数，在Optional对象中的值存在时，将其转换成另一种类型，然后返回一个包含这个类型值的心Optional对象。如果Optional对象为空，那么返回空的Optional对象
        Optional<String> flatMapOptional1 = optional.flatMap(v -> Optional.ofNullable(null));
        System.out.println("flatMap映射1：" + flatMapOptional1.get());

        // 接受一个函数作为参数，但这里的函数返回值必须是一个Optional对象，如果Optional对象中的值存在，那么将其传递给函数作为参数去执行函数，并且将函数的返回值包装在新的Optional对象中返回，如果Optional对象对象为空，那么返回空的Optional对象
        Optional<String> flatMapOptional2 = optional.flatMap(v -> Optional.ofNullable(toString()));
        System.out.println("flatMap映射2：" + flatMapOptional2.get());


        Optional<String> filterOptional = mapOptional.filter(new Predicate<String>() {
            @Override
            public boolean test(String s) {
                if (s != null) {
                    return false;
                }
                return s.equals("1");
            }
        });
        System.out.println("通过断言过滤后的值：" + filterOptional.get());


        optional = Optional.empty();
        // 为空设置默认值
        System.out.println("为空设置替代值：" + optional.orElse(100));
        // 为空设置生产者Supplier提供的默认值
        System.out.println("为空设置默认值：" + optional.orElseGet(()->100));
        // 为空抛出指定异常
        System.out.println("为空抛出指定异常：" + optional.orElseThrow(()->new RuntimeException()));


    }

}
